package com.yj.web.WeChat.api;

import com.yj.web.WeChat.Base.WxAppConstant;
import com.yj.web.WeChat.Bean.*;
import com.yj.web.WeChat.util.SHA1;
import com.yj.web.WeChat.util.StringUtils;
import com.yj.web.WeChat.util.http.*;
import com.yj.web.util.FileUtils;
import com.yj.web.util.WxError;
import com.yj.web.util.WxErrorException;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

/**
 * Created by yanlinyang on 2017/9/10.
 */
@Service("wxService")
public class WxServiceImpl implements WxService {

    protected final Object globalAccessTokenRefreshLock = new Object();
    protected final Logger log = LoggerFactory.getLogger(WxServiceImpl.class);
    @Autowired
    protected WxConfigStorage wxConfigStorage;
    //修改
    protected CloseableHttpClient httpClient = HttpClients.createDefault();;
    protected HttpHost httpProxy;

    private int retrySleepMillis = 1000;
    private int maxRetryTimes = 5;

    @Override
    public boolean checkSignature(String timestamp, String nonce, String signature) {
        try {
            String sha1 = SHA1.gen(WxAppConstant.TOKEN, timestamp, nonce);
            return sha1.equals(signature);
        } catch (Exception e) {
            return false;
        }
    }

    @Override
    public String getAccessToken() throws WxErrorException {
        return getAccessToken(false);
    }

    @Override
    public String getAccessToken(boolean forceRefresh) throws WxErrorException {
//        if (null == wxConfigStorage) {
//            WxConfigStorage configStorage = new WxInMemoryConfigStorage();
//            setWxConfigStorage(configStorage);
//        }
        if (forceRefresh) {
            wxConfigStorage.expireAccessToken();
        }
        if (wxConfigStorage.isAccessTokenExpired()) {
            synchronized (globalAccessTokenRefreshLock) {
                if (wxConfigStorage.isAccessTokenExpired()) {
                    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"
                            + "&appid=" + WxAppConstant.APP_ID
                            + "&secret=" + WxAppConstant.APP_SECRET;
                    try {
                        HttpGet httpGet = new HttpGet(url);
                        if (httpProxy != null) {
                            RequestConfig config = RequestConfig.custom().setProxy(httpProxy).build();
                            httpGet.setConfig(config);
                        }
                        CloseableHttpResponse response = getHttpclient().execute(httpGet);
                        String resultContent = new BasicResponseHandler().handleResponse(response);
                        WxError error = WxError.fromJson(resultContent);
                        if (error.getErrorCode() != 0) {
                            throw new WxErrorException(error);
                        }
                        WxAccessToken accessToken = WxAccessToken.fromJson(resultContent);
                        wxConfigStorage.updateAccessToken(accessToken.getAccessToken(), accessToken.getExpiresIn());
                    } catch (ClientProtocolException e) {
                        throw new RuntimeException(e);
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
        return wxConfigStorage.getAccessToken();
    }

    protected CloseableHttpClient getHttpclient() {
        return httpClient;
    }

    @Override
    public WxUser userInfo(String openid, String lang) throws WxErrorException {
        String url = "https://api.weixin.qq.com/cgi-bin/user/info";
        lang = lang == null ? "zh_CN" : lang;
        String responseContent = execute(new SimpleGetRequestExecutor(), url, "openid=" + openid + "&lang=" + lang);
        return WxUser.fromJson(responseContent);
    }

    public <T, E> T execute(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
        int retryTimes = 0;
        do {
            try {
                return executeInternal(executor, uri, data);
            } catch (WxErrorException e) {
                WxError error = e.getError();
                /**
                 * -1 系统繁忙, 1000ms后重试
                 */
                if (error.getErrorCode() == -1) {
                    int sleepMillis = retrySleepMillis * (1 << retryTimes);
                    try {
                        log.debug("微信系统繁忙，{}ms 后重试(第{}次)", sleepMillis, retryTimes + 1);
                        Thread.sleep(sleepMillis);
                    } catch (InterruptedException e1) {
                        throw new RuntimeException(e1);
                    }
                } else {
                    throw e;
                }
            }
        } while (++retryTimes < maxRetryTimes);

        throw new RuntimeException("微信服务端异常，超出重试次数");
    }

    protected synchronized <T, E> T executeInternal(RequestExecutor<T, E> executor, String uri, E data) throws WxErrorException {
        if (uri.indexOf("access_token=") != -1) {
            throw new IllegalArgumentException("uri参数中不允许有access_token: " + uri);
        }
        String accessToken = getAccessToken(false);

        String uriWithAccessToken = uri;
        uriWithAccessToken += uri.indexOf('?') == -1 ? "?access_token=" + accessToken : "&access_token=" + accessToken;

        try {
            return executor.execute(getHttpclient(), httpProxy, uriWithAccessToken, data);
        } catch (WxErrorException e) {
            WxError error = e.getError();
      /*
       * 发生以下情况时尝试刷新access_token
       * 40001 获取access_token时AppSecret错误，或者access_token无效
       * 42001 access_token超时
       */
            if (error.getErrorCode() == 42001 || error.getErrorCode() == 40001) {
                // 强制设置wxMpConfigStorage它的access token过期了，这样在下一次请求里就会刷新access token
                wxConfigStorage.expireAccessToken();
                return execute(executor, uri, data);
            }
            if (error.getErrorCode() != 0) {
                throw new WxErrorException(error);
            }
            return null;
        } catch (ClientProtocolException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setRetrySleepMillis(int retrySleepMillis) {
        this.retrySleepMillis = retrySleepMillis;
    }

    @Override
    public void setMaxRetryTimes(int maxRetryTimes) {
        this.maxRetryTimes = maxRetryTimes;
    }

    @Override
    public WxMaterialUploadResult materialNewsUpload(WxMaterialNews news) throws WxErrorException {
        if (news == null || news.isEmpty()) {
            throw new IllegalArgumentException("news is empty!");
        }
        String url = "https://api.weixin.qq.com/cgi-bin/material/add_news";
        String responseContent = post(url, news.toJson());
        return WxMaterialUploadResult.fromJson(responseContent);
    }

    @Override
    public WxMaterialNews materialNewsInfo(String media_id) throws WxErrorException {
        String url = "https://api.weixin.qq.com/cgi-bin/material/get_material";
        return execute(new MaterialNewsInfoRequestExecutor(), url, media_id);
    }

    public String post(String url, String postData) throws WxErrorException {
        return execute(new SimplePostRequestExecutor(), url, postData);
    }

    @Override
    public WxMaterialUploadResult materialFileUpload(String mediaType, WxMaterial material) throws WxErrorException {
        String url = "https://api.weixin.qq.com/cgi-bin/material/add_material?type=" + mediaType;
        return execute(new MaterialUploadRequestExecutor(), url, material);
    }

    //自己添加, 上传图片
    @Override
    public WxMaterialUploadResult uploadMaterial(String mediaType, String fileType, String fileName) throws WxErrorException, IOException {
//        InputStream inputStream = ClassLoader.getSystemResourceAsStream(fileName);
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream(fileName);
        File tempFile = FileUtils.createTmpFile(inputStream, UUID.randomUUID().toString(), fileType);
        WxMaterial wxMaterial = new WxMaterial();
        wxMaterial.setFile(tempFile);
        wxMaterial.setName(fileName);

        WxMaterialUploadResult res = materialFileUpload(mediaType, wxMaterial);
        return res;
    }

    public WxMaterialUploadResult addNews(String author, String thumbMediaId, String title, String content, String contentSourceUrl,
                        boolean showCoverPic, String digest) throws WxErrorException {
        WxMaterialNews wxMpMaterialNewsSingle = new WxMaterialNews();
        WxMaterialNews.WxMaterialNewsArticle mpMaterialNewsArticleSingle = new WxMaterialNews.WxMaterialNewsArticle();
        mpMaterialNewsArticleSingle.setAuthor(author);
        mpMaterialNewsArticleSingle.setThumbMediaId(thumbMediaId);
        mpMaterialNewsArticleSingle.setTitle(title);
        mpMaterialNewsArticleSingle.setContent(content);
        mpMaterialNewsArticleSingle.setContentSourceUrl(contentSourceUrl);
        mpMaterialNewsArticleSingle.setShowCoverPic(showCoverPic);
        mpMaterialNewsArticleSingle.setDigest(digest);
        wxMpMaterialNewsSingle.addArticle(mpMaterialNewsArticleSingle);

        WxMaterialUploadResult resSingle = materialNewsUpload(wxMpMaterialNewsSingle);
        return resSingle;
    }

    public void setWxConfigStorage(WxConfigStorage wxConfigProvider) {
        this.wxConfigStorage = wxConfigProvider;

        String http_proxy_host = wxConfigStorage.getHttp_proxy_host();
        int http_proxy_port = wxConfigStorage.getHttp_proxy_port();
        String http_proxy_username = wxConfigStorage.getHttp_proxy_username();
        String http_proxy_password = wxConfigStorage.getHttp_proxy_password();

        final HttpClientBuilder builder = HttpClients.custom();
        if (StringUtils.isNotBlank(http_proxy_host)) {
            // 使用代理服务器
            if (StringUtils.isNotBlank(http_proxy_username)) {
                // 需要用户认证的代理服务器
                CredentialsProvider credsProvider = new BasicCredentialsProvider();
                credsProvider.setCredentials(
                        new AuthScope(http_proxy_host, http_proxy_port),
                        new UsernamePasswordCredentials(http_proxy_username, http_proxy_password));
                builder
                        .setDefaultCredentialsProvider(credsProvider);
            } else {
                // 无需用户认证的代理服务器
            }
            httpProxy = new HttpHost(http_proxy_host, http_proxy_port);
        }
        if (wxConfigProvider.getSSLContext() != null){
            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
                    wxConfigProvider.getSSLContext(),
                    new String[] { "TLSv1" },
                    null,
                    SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
            builder.setSSLSocketFactory(sslsf);
        }
        httpClient = builder.build();
    }
}
